package com.talkweb.osmharmony.tileprovider.util;

import com.talkweb.osmharmony.util.Log;
import ohos.app.Context;
import ohos.app.Environment;
import ohos.data.usage.DataUsage;
import ohos.data.usage.MountState;
import ohos.data.usage.StatVfs;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class StorageUtils {
    public static final String SD_CARD = "sdCard";
    public static final String EXTERNAL_SD_CARD = "externalSdCard";

    private static final String TAG = "StorageUtils";

    public static class StorageInfo {
        public final String path;
        public final boolean internal;
        public boolean readonly;
        public final int display_number;
        public long freeSpace = 0;
        public String displayName;

        public StorageInfo(String path, boolean internal, boolean readonly, int display_number) {
            this.path = path;
            this.internal = internal;
            this.display_number = display_number;

            //TODO 最好通过鸿蒙API获取
            if (StatVfs.isSupported()) {
                this.freeSpace = new StatVfs(path).getFreeSpace();
            } else {
                this.freeSpace = new File(path).getFreeSpace();
            }

            if (!readonly) {
                //confirm it's writable
                this.readonly = !isWritable(new File(path));
            }

            StringBuilder res = new StringBuilder();
            if (internal) {
                res.append("Internal SD card");
            } else if (display_number > 1) {
                res.append("SD card ").append(display_number);
            } else {
                res.append("SD card");
            }
            if (readonly) {
                res.append(" (Read only)");
            }
            displayName = res.toString();
        }

        public String getDisplayName() {
            return displayName;
        }

        public void setDisplayName(String val) {
            displayName = val;
        }

        @SuppressWarnings("EqualsReplaceableByObjectsCall") // requires API19
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            StorageInfo that = (StorageInfo) o;

            if (internal != that.internal) return false;
            if (readonly != that.readonly) return false;
            if (display_number != that.display_number) return false;
            if (freeSpace != that.freeSpace) return false;
            if (path != null ? !path.equals(that.path) : that.path != null) return false;
            return displayName != null ? displayName.equals(that.displayName) : that.displayName == null;
        }

        @Override
        public int hashCode() {
            int result = path != null ? path.hashCode() : 0;
            result = 31 * result + (internal ? 1 : 0);
            result = 31 * result + (readonly ? 1 : 0);
            result = 31 * result + display_number;
            result = 31 * result + (int) (freeSpace ^ (freeSpace >>> 32));
            result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
            return result;
        }
    }

//    /**
//     * Attention! This method only gets storage locations that are context independent. Especially
//     * it does not return application specific paths like getFilesDir() or getCacheDir(), which
//     * might lead to problems especially on API29 and up due to scoped storage restrictions, where
//     * this method will always return an empty list!
//     * It's always recommended to use {@link #getStorageList(Context)} instead!
//     *
//     * @return A {@link List} of {@link StorageInfo} of all storage paths, writable or not.
//     */
//    public static List<StorageInfo> getStorageList() {
//        return getStorageList(null);
//    }

    /**
     * Detects all available storage locations, writable or not.
     * <p>
     * Attention! If context==null this method only gets storage locations that are context
     * independent. Especially it will not return application specific paths like getFilesDir() or
     * getCacheDir(), which might lead to problems especially on API29 and up due to scoped storage
     * restrictions, where this is then guaranteed to return an empty list!
     *
     * @return A {@link List} of {@link StorageInfo} of all storage paths, writable or not.
     */
    public static List<StorageInfo> getStorageList(Context context) {
        if (context != null) {
            return getStorageListApi19(context);
        }
        return null;
    }

    private static List<StorageInfo> getStorageListApi19(Context context) {
        ArrayList<StorageInfo> storageInfos = new ArrayList<>();

        storageInfos.add(new StorageInfo(context.getFilesDir().getAbsolutePath(), true, false, -1));

        ArrayList<File> storageDirs = new ArrayList<>();
        File[] externalDirs = context.getExternalFilesDirs(null);

        for (File externalDir : externalDirs) {
            // "Returned paths may be null if a storage device is unavailable."
            if (externalDir == null) {
                continue;
            }

            MountState state = DataUsage.getDiskMountedStatus(externalDir);
            if (MountState.DISK_MOUNTED == state) {
                storageDirs.add(externalDir);
            }
        }

        for (File storageDir : storageDirs) {
            storageInfos.add(new StorageInfo(storageDir.getAbsolutePath(), false, false, -1));
        }

        return storageInfos;
    }

//    /**
//     * Gets the best possible storage location by free space
//     * <p>
//     * Attention! This method only gets storage locations that are context independent. Especially
//     * it does not return application specific paths like getFilesDir() or getCacheDir(), which
//     * might lead to problems especially on API29 and up due to scoped storage restrictions.
//     * For now it is advised to manually determine a proper cache location and set it via
//     * {@link com.talkweb.osmharmony.config.IConfigurationProvider#setOsmdroidTileCache(File)}.
//     *
//     * @return A {@link StorageInfo} object.
//     */
//    public static StorageInfo getBestWritableStorage() {
//        return getBestWritableStorage(null);
//    }

    /**
     * Gets the best possible storage location by free space
     * <p>
     * Attention! If context==null this method only gets storage locations that are context
     * independent. Especially it will not return application specific paths like getFilesDir() or
     * getCacheDir(), which might lead to problems especially on API29 and up due to scoped storage
     * restrictions, where this is then guaranteed to return null!
     *
     * @return A {@link StorageInfo} object.
     */
    public static StorageInfo getBestWritableStorage(final Context context) {
        StorageInfo bestStorage = null;
        List<StorageInfo> storageList = getStorageList(context);
        for (int i = 0; i < storageList.size(); i++) {
            StorageInfo currentStorage = storageList.get(i);
            if (!currentStorage.readonly && isWritable(new File(currentStorage.path))) {
                if (bestStorage != null) {
                    //compare free space
                    if (bestStorage.freeSpace < currentStorage.freeSpace) {
                        bestStorage = currentStorage;
                    }
                } else {
                    bestStorage = currentStorage;
                }
            }
        }
        return bestStorage;
    }

    /**
     * @return True if the primary shared storage is available. False otherwise.
     * @deprecated As of 6.1.7, will be removed in the future.
     */
    @Deprecated
    public static boolean isAvailable() {
        return isPrimarySharedStorageAvailable();
    }

    /**
     * @return True if the primary shared storage is available. False otherwise.
     */
    private static boolean isPrimarySharedStorageAvailable() {
        MountState mountState = DataUsage.getDiskMountedStatus();
        return MountState.DISK_MOUNTED.equals(mountState);
    }

    /**
     * @return The path of the primary shared storage.
     * @deprecated As of 6.1.7, will be removed in the future.
     */
    @Deprecated
    public static String getSdCardPath(Context context) {
        return context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getPath() + "/";
    }

    /**
     * @return True if the primary shared storage is writable. False otherwise.
     * @deprecated As of 6.1.7, will be removed in the future.
     */
    @Deprecated
    public static boolean isWritable() {
        MountState state = DataUsage.getDiskMountedStatus();
        return MountState.DISK_MOUNTED.equals(state);
    }

    /**
     * @return True if the path is writable. False otherwise.
     */
    public static boolean isWritable(File path) {
        try {
            File tmp = new File(path.getAbsolutePath() + File.separator + UUID.randomUUID().toString());
            FileOutputStream fos = new FileOutputStream(tmp);
            fos.write("hi".getBytes());
            fos.close();
            //noinspection ResultOfMethodCallIgnored
            tmp.delete();
            Log.i(TAG, path.getAbsolutePath() + " is writable");
            return true;
        } catch (Throwable ex) {
            Log.i(TAG, path.getAbsolutePath() + " is NOT writable");
            return false;
        }
    }

}
